<?php

class RaidRow extends Zend_Db_Table_Row_Abstract
{
    
	public function getHighestTime()
	{
		// The current maximum time is set to 1 (0 is the standard join time)
		$nextTime = 0;
		
		// Get the kill with the highest time
		$killsTable = new RaidKills();
		$kill = $killsTable->fetchRow($killsTable->select()->where("raid_id = ?", $this->id)->order("time DESC"));
		
		// There could be no kills (fetchRow is null)
		if ($kill && $kill->time > $nextTime) {
			$nextTime = $kill->time;
		}
		
		// Get the change with the highest time
		$rosterChangesTable = new RaidRosterChanges();
		$rosterChange = $rosterChangesTable->fetchRow($rosterChangesTable->select()->where("raid_id = ?", $this->id)->order("time DESC"));

		
		// There could be no changes (fetchRow is null)
		if ($rosterChange && $rosterChange->time > $nextTime) {
			$nextTime = $rosterChange->time;
		}
		
		
		// Get the change with the highest time
		$dungeonChangesTable = new RaidDungeonChanges();
		$dungeonChange = $dungeonChangesTable->fetchRow($dungeonChangesTable->select()->where("raid_id = ?", $this->id)->order("time DESC"));

		
		// There could be no changes (fetchRow is null)
		if ($dungeonChange && $dungeonChange->time > $nextTime) {
			$nextTime = $dungeonChange->time;
		}
		
		return $nextTime;
	}
	
	private function getNextTime()
	{	
		// The next time is one higher then the highest
		return (1 + $this->getHighestTime());
	}
	
	
	private function _getCharactersInRaid($time=null)
	{
	    $changesTable = new RaidRosterChanges();
		$selectChanges = $changesTable->select()->where('raid_id = ?', $this->id)
												->order('time ASC');
												
		// See if a valid time has been given
		if (is_numeric($time)) {
			$selectChanges->where('time <= ?', $time);
		}

		$changes = $changesTable->fetchAll($selectChanges);
		
		$roster = array();
		
		foreach($changes as $change) {
			
			// If the change to the roster is a leave we must remove the character
			if ($change->is_leave) {
				
				// create a new empty roster and transfer all but the character that left
				$newRoster = array();
				foreach($roster as $character) {
					if ($character != $change->character_id) {
						$newRoster[] = $character;
					}
				}
				$roster = $newRoster;
				
			} else {
				$roster[] = $change->character_id;
			}
		}		
		
		return $roster;
	}
	
	
	
	
	private function getCharactersIdsInRaid($time=null)
	{
		$changesTable = new RaidRosterChanges();
		$selectChanges = $changesTable->select()->where('raid_id = ?', $this->id)
												->order('time ASC');
												
		// See if a valid time has been given
		if (is_numeric($time)) {
			$selectChanges->where('time <= ?', $time);
		}

		$changes = $changesTable->fetchAll($selectChanges);
		
		$roster = array();
		
		foreach($changes as $change) {
			
			// If the change to the roster is a leave we must remove the character
			if ($change->is_leave) {
				
				// create a new empty roster and transfer all but the character that left
				$newRoster = array();
				foreach($roster as $character) {
					if ($character != $change->character_id) {
						$newRoster[] = $character;
					}
				}
				$roster = $newRoster;
				
			} else {
				$roster[] = $change->character_id;
			}
		}		
		
		return $roster;
	}
	
	public function getCharactersNotInRaid($time=null)
	{
		// Get the roster at the time
		$rosterAtTime = $this->getCharactersIdsInRaid($time);
		
		// Select the characters from thier table
		$charactersTable = new Characters();
		$selectCharacters = $charactersTable->select();
		foreach($rosterAtTime as $id) {
			$selectCharacters->where("id != ?", $id);
		}
		
		// Return the found characters
		return $charactersTable->fetchAll($selectCharacters);
	}
	
	
	public function getCharactersInRaid($time=null)
	{
		// Get the roster at the time
		$rosterAtTime = $this->getCharactersIdsInRaid($time);
		
		// Select the characters from thier table
		$charactersTable = new Characters();
		$selectCharacters = $charactersTable->select()->where("false");
		foreach($rosterAtTime as $id) {
			$selectCharacters->orWhere("id = ?", $id);
		}
		
		// Return the found characters
		return $charactersTable->fetchAll($selectCharacters);
	}
	
	
	public function getCharactersInRaidAsIds($time=null)
	{
		/*
		// Get the roster at the time
		$rosterAtTime = $this->getCharactersIdsInRaid($time);
		
		// Select the characters from thier table
		$charactersTable = new Characters();
		$selectCharacters = $charactersTable->select()->where("false");
		foreach($rosterAtTime as $id) {
			$selectCharacters->orWhere("id = ?", $id);
		}
		
		$characters = $charactersTable->fetchAll($selectCharacters);
		$result = array();
		
		foreach ($characters as $character){
			$result[] = $character->id;
		}
		
		
		// Return the found characters
		return $result;
		*/
		return $this->getCharactersIdsInRaid($time);
	}
	
	
	
	public function isCharacterInRaidAtTime($characterId, $time)
	{
		$idsAtTime = $this->getCharactersIdsInRaid($time);
		
		foreach($idsAtTime as $id){
			if ($id == $characterId) {
				return true;
			}
		}
		
		return false;
	}
	
	public function isCharacterOrAltInRaidATime($characterId, $time){
	    $char = Characters::getById($characterId);
	    if (!$char) {
            echo $this->_helper->jsonError("Invalid characterId in RaidRow->isCharacterOrAltInRaidATime!");
            return;
        }        
        return $this->isUserInRaidAtTime($char->user_id,$time);        
	}
	
	public function isUserInRaidAtTime($userId, $time){
	    
	    //get the user and check if he exists
	    $user = Users::getById($userId);	    
	    if (!$user) {
            echo $this->_helper->jsonError("Invalid userId in RaidRow->isUserInRaidAtTime!");
            return;
        }
        
        //get all the the users characters
        $usersChars = $user->findDependentRowset('Characters','Users');

        //get all the characters in the raid
        $characterIds = $this->getCharactersIdsInRaid($time);

        //iterate through the users characters, if one of them is in the raid return true
        foreach ($usersChars as $char){
            if (key_exists($char->id)) return true; 
        }
        
        //none of the users characters was found in the raid, so return false.
        return false;
        
	}
	

	public function addJoinsForAllConfirmed()
	{
		// Test if anyone 
		$inRaid = $this->getCharactersInRaid();
		
	}
	

	private function incrementAllAfterTime($time)
	{
		if ($time == 0) return;
		$raidRosterChangesTable = new RaidRosterChanges();
		$selectRaidRosterChanges = 	$raidRosterChangesTable->select()
									->where('raid_id = ?',$this->id)
									->where('time >= ?',$time);
		$raidRosterChanges = $raidRosterChangesTable->fetchAll($selectRaidRosterChanges);
		
		if (count($raidRosterChanges) > 0){
			foreach ($raidRosterChanges as $change){
				$change->time = $change->time + 1;
				$change->save();
			}
		}
		
		$raidKillsTable = new RaidKills();
		$selectRaidKills = 	$raidKillsTable->select()
									->where('raid_id = ?',$this->id)
									->where('time >= ?',$time);
		$raidKills = $raidKillsTable->fetchAll($selectRaidKills);
		
		if (count($raidKills) > 0){
			foreach ($raidKills as $kill){
				$kill->time = $kill->time + 1;
				$kill->save();
			}
		}
		
		
		$raidDungeonChangesTable = new RaidDungeonChanges();
		$selectRaidDungeonChanges =	$raidDungeonChangesTable->select()
									->where('raid_id = ?',$this->id)
									->where('time >= ?',$time);
		$raidDungeonChanges = $raidDungeonChangesTable->fetchAll($selectRaidDungeonChanges);
		
		if (count($raidDungeonChanges) > 0){
			foreach ($raidDungeonChanges as $change){
				$change->time = $change->time + 1;
				$change->save();
			}
		}
	}
	
	public function addJoin($characterId, $note = null, $time = null)
	{
		// Test if the character is in the raid at the selected time.
		//
		// If this join is either at the beginning or at the end we test directly at the time. 
		//   (As inserting a join here will not effect other roster changes).
		//
		// Otherwise we test as if we had incremented the remaining events (ie. time-1)
		//
		if ($time === null || $time === 0) {
			if ($time === null) $time = $this->getNextTime(); 
			$inRaid = $this->isCharacterInRaidAtTime($characterId, $time);
		} else {
			$inRaid = $this->isCharacterInRaidAtTime($characterId, $time-1);
		}
		
		
		
		
		// Return false in case the character was in the raid
		if ($inRaid) return false;
		
		// In case we inserted somewhere outside the initial group we
		// increment all times after this one.
		if ($time > 0) {
			$this->incrementAllAfterTime($time);
		}
		
		// Prepare data for insertion
		$raidRosterChangesTable = new RaidRosterChanges();
		$data = array( 'is_leave'		=> false,
					   'time'			=> $time,
					   'raid_id'		=> $this->id,
					   'character_id'	=> $characterId,
					   'note'			=> $note );
		
		// Insert the join
		$result = $raidRosterChangesTable->insert($data);
		
		// Remove the next join associated with this character (if one exists)
		$selectNextJoin = $raidRosterChangesTable->select()
						->where('character_id = ?',$characterId)
						->where('time > ?',$time)
						->where('is_leave = ?',0)
						->order('time ASC');
		$nextJoin = $raidRosterChangesTable->fetchRow($selectNextJoin);		
		if ($nextJoin) $nextJoin->delete();	
		
		$this->updateAllKills($time);
		
		// Great success
		return true;
	}
	
	public function addLeave($characterId, $spent = null, $dkpEventId = null, $note = null, $time = null)
	{
		// Test if the character is in the raid at the selected time.
		//
		// If this leave is either at the beginning or at the end we test directly at the time. 
		//   (As inserting a join here will not effect other roster changes).
		//
		// Otherwise we test as if we had incremented the remaining events (ie. time-1)
		//
		if ($time === 0) {
			return false;
		} elseif ($time === null) {
			$time = $this->getNextTime(); 
			$inRaid = $this->isCharacterInRaidAtTime($characterId, $time);
		} else {
			$inRaid = $this->isCharacterInRaidAtTime($characterId, $time-1);
		}
		
		// Return false in case the character was not in the raid
		if (!$inRaid) return false;
		
		// Create a adjustment if one is needed
		$dkpAdjustmentId = null;
		if ($dkpEventId != null && $spent != null && $spent != 0){
			
			//Gets the dkp pool which is currently in use.
			$dkpPoolId;
			$currentDungeon = $this->getDungeonToTime($time);
			if($currentDungeon) $dkpPoolId = $currentDungeon->getDkpPoolId();
			else return false;
			
			$dkpAdjustmentsTable = new DkpAdjustments();
			$dkpAdjustmentId = $dkpAdjustmentsTable->createAdjustment( $dkpEventId,
			                                                           $this->start_time,
			                                                           $characterId,
			                                                           $dkpPoolId,
			                                                           null,
			                                                           $spent,
			                                                           $note )->id;
		}
		
		// Increment all times afterwards (does nothing if initial time was null)
		$this->incrementAllAfterTime($time);
		
		// Prepare data for insertion
		$raidRosterChangesTable = new RaidRosterChanges();
		$data = array( 'is_leave'			=> true,
					   'time'				=> $time,
					   'raid_id'			=> $this->id,
					   'character_id'		=> $characterId,
					   'dkp_adjustment_id'	=> $dkpAdjustmentId,
					   'note'				=> $note );
				
		// Insert the leave
		$raidRosterChangesTable->insert($data);

		// Remove the next leave associated with this character (if one exists)
		$selectNextLeave = $raidRosterChangesTable->select()
								->where('character_id = ?',$characterId)
								->where('time > ?',$time)
								->where('is_leave = ?',1)
								->order('time ASC');
		
		$nextLeave = $raidRosterChangesTable->fetchRow($selectNextLeave);		
		if ($nextLeave) $nextLeave->delete();	
		
		$this->updateAllKills($time);
		
		// Great success
		return true;
	}
	
	public function addKill($time = null, $bossId = null, $dkpEventId = null, $earned = null, $note = null)
	{
        
		
		if ($time === 0) return false;
		if ($time != null) $this->incrementAllAfterTime($time); // TODO: This is in the wrong place, what if the addKill fails?
		else $time = $this->getNextTime();
		
		if ($bossId && array_key_exists($bossId,$this->getKilledBossesToTime(null))) {
		    print_r($this->getAliveBossesToTime(null));
		    return false;
		}
		
		$dkpAdjustmentId = null;
		if ($dkpEventId != null && $earned != null && $earned != 0) {
			
			//Gets the dkp pool which is currently in use.
			$dkpPoolId;
			$currentDungeon = $this->getDungeonToTime($time);
			if($currentDungeon) $dkpPoolId = $currentDungeon->getDkpPoolId();
			else return false;
			
			$dkpAdjustmentsTable = new DkpAdjustments();
			$dkpAdjustmentId = $dkpAdjustmentsTable->createAdjustment(	$dkpEventId,
																		$this->start_time,
																		$this->getCharactersInRaid($time),
																		$dkpPoolId,
																		$earned,																		
																		null,
																		$note )->id;
		}
		
		$raidKillsTable = new RaidKills();
		$data = array(
					'time'				=> $time,
					'raid_id'			=> $this->id,
					'boss_id'			=> $bossId,
					'dkp_adjustment_id'	=> $dkpAdjustmentId,
					'note'				=> $note
				);
		$raidKillsTable->insert($data);
		return true;
		
	}
	
	/*
	public function addDrop($killId,$characterId,$itemId,$dkpSpent,$note)
	{
		if(!is_numeric($killId)){
			return false;
		}
		$raidKillsTable = new RaidKills();
		$select = $raidKillsTable->select()
					->where('id = ?',$killId);
		$raidKill = $raidKillsTable->fetchRow($select);
		
		if(!$raidKill){
			return false;
		}
		$raidKill->addDrop($characterId,$itemId,$dkpSpent,$note);
		return true;
	}*/
	
	/**
	 * Dungeon changes can only be added at the end of the current 
	 * events.
	 *
	 * @param unknown_type $dungeonId
	 * @param unknown_type $time
	 * @return unknown
	 */
	public function addDungeonChange($dungeonId)
	{
		$currentDungeon = $this->getDungeonToTime();
		if ($currentDungeon && $currentDungeon->id == $dungeonId) {
			return false;
		}
		
		$time = $this->getNextTime();
		
		$dungeonChangesTable = new RaidDungeonChanges();
		$data = array(	'raid_id'	=> $this->id,
						'time'		=> $time,
						'dungeon_id'=> $dungeonId
				);
		$dungeonChangesTable->insert($data);
		return true;
	}
	
	public function removeDungeonChange($changeId)
	{
		$dungeonChangesTable = new RaidDungeonChanges();
		$select = $dungeonChangesTable->select()->where('id = ?',$changeId);
		$dungeonChange = $dungeonChangesTable->fetchRow($select);
		
		if(!$dungeonChange || $dungeonChange->raid_id != $this->id) {
			return false;
		}
		
		if ($dungeonChange->time != $this->getHighestTime())
		{
			return false;
		}
		
		$dungeonChange->delete();
		return true;
	}
	
	/*
	 * TODO: Highly suspicious function... takes a drop row while everything else takes ids?
	 */
	public function removeDrop($raidDropRow)
	{
		if($raidDropRow->findParentRow('RaidKills','RaidKills')->findParentRow('Raids','Raids')->id != $this->id) {
			return false;
		}		
		$raidDropRow->delete();
		return true;			
	}
	
	/*
	 * Updates the participants of all the RaidKills in $this
	 * raid after time
	 * 
	 * @param int time of the event that triggers this update, if no param is
	 * submitted all RaidKills will be updated.
	 */
	public function updateAllKills($time = 0)
	{
		if($time == null){
			echo "null is not a valid time in RaidRow->updateAllKills";
			return;
		}
		
		//Retrieving all the RaidKills from �this raid
		$raidKillsTable = new RaidKills();
		$select = $raidKillsTable->select()
					->where('raid_id = ?',$this->id)
					->where('time >= ?', $time);
		$raidKills = $raidKillsTable->fetchAll($select);
		
		//updating each of the participants lists for each of the RaidKills

		foreach($raidKills as $kill)
		{
			if ($kill->dkp_adjustment_id != null){
				$dkpAdjustmentRow = $kill->findParentRow('DkpAdjustments','DkpAdjustments');
				$dkpAdjustmentRow->setCharacters($this->getCharactersInRaidAsIds($kill->time));
			}
			
		}
	}
	
	public function removeJoin($eventId)
	{		
		$raidRosterChangesTables = new RaidRosterChanges();
		$selectJoin = $raidRosterChangesTables->select()
						->where('id = ?',$eventId);
		$rosterChange = $raidRosterChangesTables->fetchRow($selectJoin);
		
		if($rosterChange == null || $rosterChange->is_leave || $rosterChange->raid_id != $this->id){
			return false;
		}
		
		$time = $rosterChange->time;
		
		$selectNextLeave = $raidRosterChangesTables->select()
						->where('character_id = ?',$rosterChange->character_id)
						->where('time > ?',$time)
						->where('is_leave = ?', 1)
						->order('time ASC');
						
		$nextLeave = $raidRosterChangesTables->fetchRow($selectNextLeave);		
		
		//deleting this join and the same characters next leave
		$rosterChange->delete();
		if ($nextLeave)$nextLeave->delete();
		
		$this->updateAllKills($time);
		
		return true;
	}
	
	public function removeLeave($eventId)
	{		
		$raidRosterChangesTables = new RaidRosterChanges();
		$selectLeave = $raidRosterChangesTables->select()
						->where('id = ?',$eventId);
		$rosterChange = $raidRosterChangesTables->fetchRow($selectLeave);
		
		if($rosterChange == null || !$rosterChange->is_leave || $rosterChange->raid_id != $this->id) {
			return false;
		}
		
		$time = $rosterChange->time;
		
		$selectNextJoin = $raidRosterChangesTables->select()
						->where('character_id = ?',$rosterChange->character_id)
						->where('time >= ?',$time)
						->where('is_leave = ?',0)
						->order('time ASC');
		
		$nextJoin = $raidRosterChangesTables->fetchRow($selectNextJoin);		
		
		//deleting this join and the same characters next leave
		$rosterChange->delete();
		if ($nextJoin)$nextJoin->delete();
		
		$this->updateAllKills($time);
		
		return true;
	}
	
	public function removeKill($killId)
	{
		$raidKillsTable = new RaidKills();
		$selectRaidKill = $raidKillsTable->select()
							->where('id = ?',$killId);
		$raidKill = $raidKillsTable->fetchRow($selectRaidKill);
		
		if($raidKill == null || $raidKill->raid_id != $this->id) {
			return false;
		}
		
		$raidKill->delete();
		
		return true;
	}
	
	
	
	/*
	 * Returns all the bosses associated with the raid that has not yet been killed
	 * 
	 * @return array = boss_id => boss_name
	 */
	public function getAliveBossesToTime($time = null)
	{
		$allBosses = $this->getAllBossesToTime($time);
		$killedBosses = $this->getKilledBossesToTime();
		return array_diff_key($allBosses,$killedBosses);
	}
	
	
	/*
	 * Returns all the bosses that are associated with the raid, dead or alive
	 * 
	 * @return array = boss_id => boss_name
	 */
	public function getAllBossesToTime($time = null)
	{		
		
		$dungeonsRow = $this->getDungeonToTime($time);
		if ($dungeonsRow == null) {
		    throw new Exception("Raid is not in a dungeon yet");
		}
		
		$result = array();		

		$bossesRowset = $dungeonsRow->findDependentRowset('Bosses','Dungeons');
		foreach($bossesRowset as $boss){
			$result[$boss->id] = $boss->name;
		}		
		return $result;		
	}
	
	
	/*
	 * Returns all the bosses that has been killed on the raid
	 * @return array = boss_id => boss_name 
	 */
	public function getKilledBossesToTime($time = null)
	{
		$result = array();
		$raidKills = $this->findDependentRowset('RaidKills','Raids');
		
		foreach ($raidKills as $kill){
			if ($kill->time <= $time || $time == null){
				$result[$kill->boss_id] = $kill->findParentRow('Bosses','Bosses')->name;
			}
		}
		return $result;
	}
	
	public function getDungeonToTime($time = null)
	{
		$dungeonChangesTable = new RaidDungeonChanges();
		$select = $dungeonChangesTable->select()
					->where("raid_id = ?",$this->id)
					->order('time DESC');
		if($time != null){
			$select->where('time < ?',$time);
		}
		
		$dungeonChange = $dungeonChangesTable->fetchRow($select);
		if($dungeonChange) return $dungeonChange->findParentRow('Dungeons','Dungeons');
		else return null;
		
	}
	
}
